#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
主题: 实现一个简单的递归下降分析器
问题: 你想根据一组语法规则解析文本并执行命令，或者构造一个代表输入的抽象语法树。 如果语法非常简单，你可以不去使用一些框架，而是自己写这个解析器。
提示:
"""
#  算数运算表达式解析

import re
import collections

# 算数运算表达式关键字
NUM = r'(?P<NUM>\d+)'       # 数
PLUS = r'(?P<PLUS>\+)'      # 加
MINUS = r'(?P<MINUS>-)'     # 减
TIMES = r'(?P<TIMES>\*)'    # 乘
DIVIDE = r'(?P<DIVIDE>/)'   # 除
LPAREN = r'(?P<LPAREN>\()'  # 左括号
RPAREN = r'(?P<RPAREN>\))'  # 右括号
WS = r'(?P<WS>\s+)'         # 空格

master_pat = re.compile(
    '|'.join([NUM, PLUS, MINUS, TIMES, DIVIDE, LPAREN, RPAREN, WS]))

# 命名元组：标识器
Token = collections.namedtuple('Token', ['type', 'value'])  

def generate_tokens(text):
    scanner = master_pat.scanner(text)
    for m in iter(scanner.match, None):
        tok = Token(m.lastgroup, m.group())
        if tok.type != 'WS':  # 忽略空格
            yield tok

# 解析器
class ExpressionEvaluator:
    '''
    Implementation of a recursive descent parser. Each method
    implements a single grammar rule. Use the ._accept() method
    to test and accept the current lookahead token. Use the ._expect()
    method to exactly match and discard the next token on on the input
    (or raise a SyntaxError if it doesn't match).    
    '''

    def parse(self, text):
        self.tokens = generate_tokens(text)
        self.tok = None         # Last symbol consumed
        self.nexttok = None     # Next symbol tokenized
        self._advance()         # Load first Lookahead token
        return self.expr()

    def _advance(self):
        'Advance on token ahead'
        self.tok, self.nexttok = self.nexttok, next(self.tokens, None)
        print('cur: {} nex: {}'.format(self.tok, self.nexttok))

    def _accept(self, toktype):
        'Test and consume the next token if it matches toktype'
        if self.nexttok and self.nexttok.type == toktype:
            self._advance()
            return True
        else:
            return False

    def _expect(self, toktype):
        'Consume next token if it matches toktype or raise SyntaxError'
        if not self._accept(toktype):
            raise SyntaxError('Expected ' + toktype)

    # Grammar rules follow
    def expr(self):
        "expression ::= term { ('+'|'-') term }*"
        exprval = self.term()
        while self._accept('PLUS') or self._accept('MINUS'):
            op = self.tok.type
            right = self.term()
            if op == 'PLUS':
                exprval += right
            elif op == 'MINUS':
                exprval -= right
        return exprval

    def term(self):
        "term ::= factor { ('*'|'/') factor }*"
        termval = self.factor()
        while self._accept('TIMES') or self._accept('DIVIDE'):
            op = self.tok.type
            right = self.term()
            if op == 'TIMES':
                termval *= right
            elif op == 'DIVIDE':
                termval /= right
        return termval

    def factor(self):
        "factor ::= NUM | ( expr )"
        if (self._accept('NUM')):
            return int(self.tok.value)
        elif self._accept('LPAREN'):
            exprval = self.expr()
            self._expect('RPAREN')
            return exprval
        else:
            raise SyntaxError('Expected NUMBER or LPAREN')

def recipe1():
    e = ExpressionEvaluator()
    print(f"{e.parse('2') = }")
    print(f"{e.parse('2 + 3') = }")
    print(f"{e.parse('2 + 3 * 4') = }")
    print(f"{e.parse('2 + (3 + 4) * 5') = }")
    # print(e.parse('2 + (3 + * 4)'))

def main():
    print('recipe1'.center(20, '*'))
    recipe1()

if __name__ == '__main__':
    main()   